package com.viknix.threadpool.manager.config.dump;

import com.viknix.threadpool.manager.common.util.GroupKeyUtil;
import com.viknix.threadpool.manager.common.util.Md5Util;
import com.viknix.threadpool.manager.config.constant.Constants;
import com.viknix.threadpool.manager.config.model.CacheItem;
import com.viknix.threadpool.manager.server.dependence.monitor.NotifyCenter;
import com.viknix.threadpool.manager.server.dependence.monitor.event.LocalDataChangeEvent;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Dongqi
 * @Date: 2021/11/22 10:50
 * @Version 1.0
 * @Description: Config service.
 */
@Slf4j
public class ConfigCacheService {

    private static final String NO_SPACE_CN = "设备上没有空间";

    private static final String NO_SPACE_EN = "No space left on device";

    private static final String DISK_QUATA_CN = "超出磁盘限额";

    private static final String DISK_QUATA_EN = "Disk quota exceeded";

    static final Logger LOGGER = LoggerFactory.getLogger(ConfigCacheService.class);

    /**
     * groupKey -> cacheItem.
     */
    private static final ConcurrentHashMap<String, CacheItem> CACHE = new ConcurrentHashMap<String, CacheItem>();

    /**
     * 保存配置文件并更新缓存中的 md5 值。
     *
     * @param dataId         dataId string value.
     * @param group          group string value.
     * @param tenant         tenant string value.
     * @param content        content string value.
     * @param lastModifiedTs lastModifiedTs.
     * @return dumpChange success or not.
     */
    public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs) {
        String groupKey = GroupKeyUtil.getKey(dataId, group, tenant);
        // 获取写锁
        final int lockResult = tryWriteLock(groupKey);
        assert (lockResult != 0);

        // 如果当前任务执行失败，会被重新提交到队列中，任务不会丢失
        if (lockResult < 0) {
            log.warn("[dump-error] write lock failed. {}", groupKey);
            return false;
        }

        try {
            final String md5 = Md5Util.md5Hex(content, Constants.ENCODE);

            if (md5.equals(ConfigCacheService.getContentMd5(groupKey))) {
                log.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, "
                                + "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey),
                        lastModifiedTs);
            }
            // 缓存配置信息的MD5到内存当中，并且发布 LocalDataChangeEvent 事件
            updateMd5(groupKey, md5, lastModifiedTs);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("[dump-exception] save disk error.");
            return false;
        } finally {
            releaseWriteLock(groupKey);
        }
    }

    public static long getLastModifiedTs(String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        return (null != item) ? item.getLastModifiedTs() : 0L;
    }

    /**
     * Get and return content md5 value from cache. Empty string represents no data.
     */
    public static String getContentMd5(String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        return (null != item) ? item.getMd5() : Constants.NULL;
    }

    /**
     * Delete config file, and delete cache.
     *
     * @param dataId dataId string value.
     * @param group  group string value.
     * @param tenant tenant string value.
     * @return remove success or not.
     */
    public static boolean remove(String dataId, String group, String tenant) {
        final String groupKey = GroupKeyUtil.getKey(dataId, group, tenant);
        final int lockResult = tryWriteLock(groupKey);

        // If data is non-existent.
        if (0 == lockResult) {
            log.info("[remove-ok] {} not exist.", groupKey);
            return true;
        }

        // try to lock failed
        if (lockResult < 0) {
            log.warn("[remove-error] write lock failed. {}", groupKey);
            return false;
        }

        try {
            CACHE.remove(groupKey);
            NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
            return true;
        } finally {
            releaseWriteLock(groupKey);
        }
    }

    /**
     * Update md5 value.
     *
     * @param groupKey       groupKey string value.
     * @param md5            md5 string value.
     * @param lastModifiedTs lastModifiedTs long value.
     */
    public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
        // 获取缓存项
        CacheItem cache = makeSure(groupKey);
        // 如果缓存的md5和当前的md5不匹配，发布事件通知客户端
        if (cache.getMd5() == null || !cache.getMd5().equals(md5)) {
            cache.setMd5(md5);
            cache.setLastModifiedTs(lastModifiedTs);
            NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
        }
    }

    /**
     * Try to add read lock. If it succeeded, then it can call {@link #releaseWriteLock(String)}.And it won't call if
     * failed.
     *
     * @param groupKey groupKey string value.
     * @return 0 - No data and failed. Positive number - lock succeeded. Negative number - lock failed。
     */
    public static int tryReadLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1);
        if (result < 0) {
            log.warn("[read-lock] failed, {}, {}", result, groupKey);
        }
        return result;
    }

    /**
     * Release readLock.
     *
     * @param groupKey groupKey string value.
     */
    public static void releaseReadLock(String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        if (null != item) {
            item.rwLock.releaseReadLock();
        }
    }

    /**
     * Try to add write lock. If it succeeded, then it can call {@link #releaseWriteLock(String)}.And it won't call if
     * failed.
     *
     * @param groupKey groupKey string value.
     * @return 0 - No data and failed. Positive number 0 - Success. Negative number - lock failed。
     */
    static int tryWriteLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryWriteLock() ? 1 : -1);
        if (result < 0) {
            log.warn("[write-lock] failed, {}, {}", result, groupKey);
        }
        return result;
    }

    static void releaseWriteLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        if (null != groupItem) {
            groupItem.rwLock.releaseWriteLock();
        }
    }

    static CacheItem makeSure(final String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        if (null != item) {
            return item;
        }
        CacheItem tmp = new CacheItem(groupKey);
        item = CACHE.putIfAbsent(groupKey, tmp);
        return (null == item) ? tmp : item;
    }
}

